package com.wolfeyes.common.utils.reflect.proxy.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.complex.InterfaceA;
import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.complex.InterfaceB;
import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.complex.PersonA;
import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.complex.PersonB;
import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.simple.IPerson;
import com.wolfeyes.common.utils.reflect.proxy.jdkproxy.simple.Person;

/**
 * 解决方案1-静态代理：
 * 1.目地是将业务代码与日志代码完全分离,实现松散耦合.
 * 2.代理对象与被代理对象必须实现同一接口,在代理对象中实现与日志记录的相关服务,
 * 并在需要的时候呼叫被代理对象,而被代理对象只保留业务代码.
 * 静态代理的弊端：
 * 一个代理接口只能服务于一种类型的对象.对于稍大点的项目根本无法胜任.
 * 
 * 解决方案2-动态代理：
 * 1.在JDK1.3之后加入了可协助开发的动态代理功能.不必为特定对象与方法编写特定的代理对象,使用动态代理,
 * 可以使得一个处理者(Handler)服务于各个对象.
 * 2.一个处理者的类设计必须实现java.lang.reflect.InvocationHandler接口.
 * 3.通过InvocationHandler接口实现的动态代理只能代理接口的实现类.
 *
 * @description 生产代理对象的工厂(工具类)
 * @projectName bootdo
 * @className DynamicProxyFactory.java
 * @author yanyl
 * @email 1991yanyl@163.com
 * @date 2018-07-11 15:53:58
 * @since 2017年02月02日下午5:37:39
 * @createTime 2018年06月09日 下午17:04:13
 * @version 1.0.0
 */
public class JDKDynamicProxyFactory {


	/**
	 * @title: 快速获取代理类对象
	 * @see {@link Proxy #newProxyInstance(ClassLoader, Class[], InvocationHandler)}
	 * @description: TODO(随机生成编号)
	 * @param targetObject 为被代理对象
	 * @return proxyObject 代理类对象实例
	 * @author yanyl
	 * @throws Exception
	 */
	public static Object getProxy(Object targetObject) {

		JDKDynamicProxyHandler dynamicProxyHandler = new JDKDynamicProxyHandler();

		dynamicProxyHandler.setTarget(targetObject);

		// 定义代理类的类加载器classLoader
		ClassLoader classLoader = targetObject.getClass().getClassLoader();

		// 确定此对象所表示的类或接口实现的接口。
		// 也即：代理类所要实现的接口列表interfaces
		Class<?>[] interfaces = targetObject.getClass().getInterfaces();

		// 指派方法所要调用的调用处理程序invocationHandler
		InvocationHandler invocationHandler = dynamicProxyHandler;

		// 返回一个指定接口的代理类对象实例，该接口可以将方法调用指派到指定的调用处理程序。
		// 即：一个由指定的类加载器定义，并实现指定的接口，且带有代理类的指定调用处理程序的代理实例.
		// 详见 JDK API 1.6.0 中文版
		Object proxyObject = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

		// 返回代理类对象实例
		return proxyObject;

	}

	/**
	 * 测试方法main()
	 * @param args 参数数组
	 */
	public static void main(String[] args) {

		// 开始测试时间：startTime
		long startTime = System.currentTimeMillis();
		
		/*开始测试：
		 * */
		
		
		// 测试场景一：
		
		// 被代理类Person对象实例
		Person person = new Person();
		
		// 这是我们要代理的真实对象
		IPerson proxyPerson = (IPerson) JDKDynamicProxyFactory.getProxy(person);

		// 返回代理类,代理类是JVM在内存中动态创建的,该类实现传入的接口数组的全部接口(的全部方法).

		proxyPerson.eating();

		proxyPerson.sleep();
		
		
		// 测试场景二：

		// 被代理类PersonA对象实例
		PersonA personA = new PersonA();
		
		// 这是我们要代理的真实对象
		InterfaceA proxyPersonA = (InterfaceA) JDKDynamicProxyFactory.getProxy(personA);
		InterfaceB proxyPersonA2 = (InterfaceB) JDKDynamicProxyFactory.getProxy(personA);

		// 返回代理类,代理类是JVM在内存中动态创建的,该类实现传入的接口数组的全部接口(的全部方法).

		proxyPersonA.eating();

		proxyPersonA.sleep();
		
		proxyPersonA2.read();
		
		proxyPersonA2.write();
		
		// 代理类重写了toString()
		System.out.println(proxyPersonA);
		
		// 代理类重写了toString()
		System.out.println(proxyPersonA2);
		
		// 代理类重写了toString()
		// false
		System.out.println(proxyPersonA.toString() == proxyPersonA2.toString());
		
		// true
		System.out.println(proxyPersonA.toString().equals(proxyPersonA2.toString()));
		
		// false
		System.out.println(proxyPersonA == proxyPersonA2);
		
		// false
		System.out.println(JDKDynamicProxyFactory.getProxy(personA) == JDKDynamicProxyFactory.getProxy(personA));
		
		
		// 测试场景三：
		
		// 被代理类PersonA对象实例
		PersonA personA3 = new PersonA();
		
		// 被代理类PersonA对象实例
		PersonB personB = new PersonB();
		
		// 这是我们要代理的真实对象
		InterfaceA proxyPersonA3 = (InterfaceA) JDKDynamicProxyFactory.getProxy(personA3);
		InterfaceA proxyPersonB = (InterfaceA) JDKDynamicProxyFactory.getProxy(personB);

		// 返回代理类,代理类是JVM在内存中动态创建的,该类实现传入的接口数组的全部接口(的全部方法).

		proxyPersonA3.eating();

		proxyPersonA3.sleep();
		
		proxyPersonB.eating();
		
		proxyPersonB.sleep();
		
		// 结束测试时间：startTime
		long endTime = System.currentTimeMillis();
		
		// 耗时：单位（秒）
		System.out.println("用时:" + (endTime - startTime) / 1000d + "秒");
		
	}

}

